package com.google.projectulator;

import groovy.lang.Binding;
import groovy.lang.GroovyShell;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import com.google.projectulator.exception.ProcessingException;
import com.google.projectulator.exception.ProjectulatorConfigurationException;
import com.google.projectulator.exception.ProjectulatorException;

public class GroovyProcessor extends BaseProjectulatorDelegate implements Processor {
	public static final String RESOURCES_DIR = "src" + File.separator + "main" + File.separator + "resources" + File.separator;
	public static final String DEFAULT_SCRIPT_DIR = "build-scripts/";
	public static final String CONTINUE_VAR = "$CONTINUE";
	protected Map<String, String> binding;
	protected InputStream scriptStream;
	protected String source;
	
	public void init(Config config) {
		log.debug("Calling init on " + name);
		if (name == null) {
			throw new ProjectulatorConfigurationException("Groovy processors must have a name configured");
		}
		try {
			if (source == null) {
				String filePath = DEFAULT_SCRIPT_DIR + name + ".groovy";
				log.debug("Finding script file: " + filePath);
				if (scriptStream == null) {
					log.debug("No default in current project, checking GroovyProcessor classloader");
					scriptStream = getClass().getClassLoader().getResourceAsStream(filePath);
				}
				if (scriptStream == null) {
					log.debug("Script not found w/GroovyProcessor classloader.  Trying context class loader");
					scriptStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);
				}
				if (scriptStream == null) {
					throw new ProjectulatorConfigurationException("Unable to find groovy script in path or classpath");
				}
			}
		} catch (Exception e) {
			throw new ProjectulatorConfigurationException("Unable to load default file", e);
		}
	}
	
	@Override
	public void cleanup(Config config) throws ProjectulatorException {
		if (scriptStream != null) {
			try {
				scriptStream.close();
				scriptStream = null;
			} catch (IOException e) {
				throw new ProjectulatorException("Error cleaning up after GroovyProcessor(" + name + "): " + e.getMessage(), e);
			}
		}
	}
	
	public final void process(Context context) throws ProcessingException {
		log.debug("Calling process on " + name);
		try {
			Binding runtimeBinding = new Binding();
			loadBinding(context, runtimeBinding);
			loadBindingWithGlobals(context, runtimeBinding);
			log.debug("Evaluating groovy code");
			executeGroovyAgainst(runtimeBinding);
			postExecution(context, runtimeBinding);
		} catch (Exception e) {
			throw new ProcessingException("Exception evaluating groovy script: " + name, e);
		}
	}

	protected void postExecution(Context context, Binding runtimeBinding) {}
	
	protected void loadBinding(Context context, Binding runtimeBinding) {}
	
	private Binding loadBindingWithGlobals(Context context, Binding runtimeBinding) {
		log.debug("Binding variables to groovy context");
		if (binding != null) {
			for(String name : binding.keySet()) {
				runtimeBinding.setVariable(name, binding.get(name));
			}
		}
		
		runtimeBinding.setVariable("name", name);
		runtimeBinding.setVariable("context", context);
		runtimeBinding.setVariable("processor", this);
		runtimeBinding.setVariable("log", log);
		runtimeBinding.setVariable(CONTINUE_VAR, Boolean.TRUE);
		
		return runtimeBinding;
	}
	
	protected void executeGroovyAgainst(Binding runtimeBinding) {
		GroovyShell shell = new GroovyShell(getClass().getClassLoader(), runtimeBinding);
		if (source == null) {
			shell.evaluate(scriptStream);
		} else {
			shell.evaluate(source);
		}
	}
	
	public String getSource() {
		return source;
	}
	
	public void setSource(String source) {
		this.source = source;
	}
	
	public Map<String, String> getBinding() {
		return binding;
	}
	
	public void setBinding(Map<String, String> binding) {
		this.binding = binding;
	}
	
	public InputStream getScriptStream() {
		return scriptStream;
	}
	
	public void setScriptStream(InputStream scriptStream) {
		this.scriptStream = scriptStream;
	}
	
	@Override
	public String toString() {
		return "GroovyProcessor(" + name + ")";
	}
}
